/*
 * CITS2230 Operating Systems - Programming Project
 * Author:           Guilherme R. Lampert
 * Student number:   21203005
 */

#include "Scheduler.hpp"
#include "FcfsScheduler.hpp"
#include "RoundRobinScheduler.hpp"

Scheduler * Scheduler::Create(int argc, const char * argv[])
{
	// Command line parsing:
	// Examples:
	// processSim [-m mem_dump_timestep] FCFS in_file [out_file]
	// processSim [-m mem_dump_timestep] RR time_quantum in_file [out_file]

	if (argc < 3)
	{
		if ((argc == 2) && (strcmp(argv[1], "--help") == 0))
		{
			// Display help and quit.
			std::cout << "Process Scheduling Simulation" << std::endl;
			std::cout << "By Guilherme R. Lampert" << std::endl;
			std::cout << "Command line examples:" << std::endl;
			std::cout <<"./processSim [-m mem_dump_timestep] FCFS in_file [out_file]" << std::endl;
			std::cout <<"./processSim [-m mem_dump_timestep] RR time_quantum in_file [out_file]" << std::endl;
			std::cout << "Available scheduling policies are Round Robin (RR) and First Come First Served (FCFS)" << std::endl;
			std::cout << "If no output file is given, output is redirected to the standard output (stdout)" << std::endl;
			return (0);
		}

		throw std::invalid_argument("Not enough arguments! Usage:\n"
				"./processSim [-m mem_dump_timestep] FCFS in_file [out_file]\n"
				"./processSim [-m mem_dump_timestep] RR time_quantum in_file [out_file]\n"
				"Try ./processSim --help to see help text");
	}

	Scheduler * scheduler = 0;
	std::string schedPolicy, procListFile, outputFile;
	int memDumpTimeStep, RRTimeQuantum;

	if (strcmp(argv[1], "-m") == 0) // With memory dumps:
	{
		memDumpTimeStep = ParseInt(argv[2]);
		schedPolicy = argv[3];

		if ((schedPolicy == "FCFS") || (schedPolicy == "fcfs"))
		{
			procListFile = argv[4];
			outputFile = (argc > 5) ? argv[5] : "";
			scheduler = new FcfsScheduler(memDumpTimeStep, procListFile, outputFile);
		}
		else if ((schedPolicy == "RR") || (schedPolicy == "rr"))
		{
			if (argc < 6)
			{
				throw std::invalid_argument("Incorrect command line for the RR policy!\n"
						"Example: ./processSim [-m <mem_dump_timestep] RR time_quantum in_file [out_file]");
			}

			RRTimeQuantum = ParseInt(argv[4]);
			procListFile = argv[5];
			outputFile = (argc > 6) ? argv[6] : "";
			scheduler = new RoundRobinScheduler(memDumpTimeStep, procListFile, outputFile, RRTimeQuantum);
		}
		else
		{
			throw std::invalid_argument("Unknown scheduling policy! Must be either FCFS or RR.");
		}
	}
	else // No memory dumps:
	{
		memDumpTimeStep = -1; // -1 disables memory dumps
		schedPolicy = argv[1];

		if ((schedPolicy == "FCFS") || (schedPolicy == "fcfs"))
		{
			procListFile = argv[2];
			outputFile = (argc > 3) ? argv[3] : "";
			scheduler = new FcfsScheduler(memDumpTimeStep, procListFile, outputFile);
		}
		else if ((schedPolicy == "RR") || (schedPolicy == "rr"))
		{
			if (argc < 4)
			{
				throw std::invalid_argument("Incorrect command line for the RR policy!\n"
						"Example: ./processSim RR time_quantum in_file [out_file]");
			}

			RRTimeQuantum = ParseInt(argv[2]);
			procListFile = argv[3];
			outputFile = (argc > 4) ? argv[4] : "";
			scheduler = new RoundRobinScheduler(memDumpTimeStep, procListFile, outputFile, RRTimeQuantum);
		}
		else
		{
			throw std::invalid_argument("Unknown scheduling policy! Must be either FCFS or RR.");
		}
	}

	return (scheduler);
}

Scheduler::Scheduler(int memDumpTimeStep,
		const std::string & procListFile,
		const std::string & outputFile)
		: memDumpTimeStep(memDumpTimeStep),
		stepsElapsedSinceLastMemDump(0),
		simulationClock(0),
		memoryManager(), processQueue(), outFile()
{
	DLog("Creating process scheduler...");

	if (memDumpTimeStep >= 0)
	{
		DLog("Memory dump time step = " << memDumpTimeStep);
	}
	else
	{
		DLog("Memory dumps disabled!");
	}

	DLog("Process list file: " << procListFile.c_str());

	if (outputFile != "")
	{
		DLog("Output file: " << outputFile.c_str());
		outFile.open(outputFile.c_str());
	}
	else
	{
		DLog("Output file: console/stdout");
	}

	ParseProcessListFile(procListFile);

	DLog("Sorting processes by arrival time");

	std::sort(processQueue.begin(), processQueue.end());

	#ifdef VERBOSE
	DLog("Done sorting, printing process queue:");
	for (size_t i = 0; i < processQueue.size(); ++i)
	{
		DLog("process[" << i << "] \"" << processQueue[i].Name()
				<< "\" Arriving at time " << processQueue[i].ArrivalTime());

		processQueue[i].Dump();
	}
	#endif // VERBOSE
}

Scheduler::~Scheduler()
{
	DLog("Destroying process scheduler...");
}

void Scheduler::RunSimulation()
{
	if (memDumpTimeStep >= 0)
	{
		RunWithMemorySimulation();
	}
	else
	{
		RunWithoutMemorySimulation();
	}
}

std::ostream & Scheduler::Output()
{
	return ((outFile.is_open()) ? outFile : std::cout);
}

void Scheduler::ParseProcessListFile(const std::string & procListFile)
{
	DLog("Parsing process list file");

	std::ifstream inFile(procListFile.c_str(), std::ios::in);

	if (inFile.is_open() && inFile.good())
	{
		char line[1024];

		while (inFile.getline(line, sizeof(line)))
		{
			// Get rid of the line break ('\n')
			for (char * l = line; *l; ++l)
			{
				if (*l == '\n')
				{
					*l = '\0';
				}
			}

			DLog("Found process file: " << line << ". Parsing it...");

			ParseProcess(line);
		}
	}
	else
	{
		std::string err = "Failed to open process list file \"";
		err.append(procListFile);
		err.append("\"");
		throw std::runtime_error(err);
	}
}

void Scheduler::ParseProcess(const std::string & procFile)
{
	std::ifstream inFile(procFile.c_str(), std::ios::in);

	if (inFile.is_open() && inFile.good())
	{
		int arrivalTime;
		char line[1024];

		// First line is the arrival time of this process,
		// in time units [0,n]
		inFile.getline(line, sizeof(line));
		arrivalTime = ParseInt(line);

		// Next lines are the "code" lines of a process, until the end of the file.
		std::vector<std::string> codeLines;

		while (inFile.getline(line, sizeof(line)))
		{
			// Get rid of the line break ('\n')
			for (char * l = line; *l; ++l)
			{
				if (*l == '\n')
				{
					*l = '\0';
				}
			}

			codeLines.push_back(line);
		}

		// Done, add process to queue
		processQueue.push_back(Process(procFile, arrivalTime, codeLines));
	}
	else
	{
		std::string err = "Failed to open process description file \"";
		err.append(procFile);
		err.append("\"");
		throw std::runtime_error(err);
	}
}
